﻿using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Net.Sockets;
using System.Net;
using System.Windows.Forms;
using System.IO;

namespace Client.p2p
{
    public class FileAttr
    {
        public int No;
        public string FileName;
        public long FileSize;
        public string SourceName;
        static private int BaseNo = 0;

        public FileAttr()
        {
        }

        public FileAttr(string name, long size)
        {
            this.No = BaseNo++;
            this.FileName = name;
            this.FileSize = size;
        }

        public FileAttr(int no, string name, long size)
        {
            this.No = no;
            this.FileName = name;
            this.FileSize = size;
        }
    }

    public class FileTransfer
    {
        private FileAttr fileAttr;
        private BackgroundWorker bwListener = null;
        private TcpListener tcpListener;
        private BackgroundWorker bwSender;
        private int listenPort = 19000;
        private int listenBackLog = 1;
        private System.Threading.Semaphore semaphore = new System.Threading.Semaphore(0, 1);
        static private int TransferBytesPerTime = 1024 * 5;

        public FileTransfer(FileAttr fileAttr)
        {
            this.fileAttr = fileAttr;
        }

        #region Receive Methods

        private void bwListener_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            OnTransferFinished(new FileTransferFinishedEventArgs((bool)e.Result && !e.Cancelled, e.Cancelled, (bool)e.Result));
            ((BackgroundWorker)sender).Dispose();
            GC.Collect();
        }

        private void bwListener_DoWork(object sender, DoWorkEventArgs e)
        {
            while (true)
            {
                try
                {
                    this.tcpListener = new TcpListener(IPAddress.Any, this.listenPort);
                    this.tcpListener.Start(this.listenBackLog);
                    this.semaphore.Release();
                    System.Diagnostics.Debug.Print("FileTransfer TcpWorker Begin");
                    e.Result = DoReceive(this.tcpListener.AcceptTcpClient());
                    break;
                }
                catch (SocketException ex)
                {
                    if (((SocketException)ex).SocketErrorCode == SocketError.AddressAlreadyInUse)
                    {
                        this.listenPort++;
                        continue;
                    }
                    else
                    {
                        MessageBox.Show(ex.Message);
                        e.Result = false;
                        break;
                    }
                }
                catch (Exception ex)
                {
                    System.Diagnostics.Debug.Print(ex.Message);
                    e.Result = false;
                    break;
                }
            }
            tcpListener.Stop();
        }

        private bool DoReceive(TcpClient tcpClient)
        {
            bool ret;
            FileTransferProgressChangedEventArgs eventArgs = new FileTransferProgressChangedEventArgs(this.fileAttr);
            OnProgressChanged(eventArgs);
            NetworkStream networkStream = tcpClient.GetStream();
            File.Delete(this.fileAttr.FileName);
            FileStream fileStream = File.Open(this.fileAttr.FileName + ".tmp", FileMode.Create, FileAccess.Write);
            byte[] buffer = new byte[TransferBytesPerTime];
            int readbyte;
            try
            {
	            while (!this.bwListener.CancellationPending && tcpClient.Client.Connected)
	            {
	                readbyte = networkStream.Read(buffer, 0, buffer.Length);
                    if (readbyte == 0) break;
	                fileStream.Write(buffer, 0, readbyte);
	                OnProgressChanged(eventArgs.Transfer(readbyte));
	            }
                ret = true;
            }
            catch (System.Exception ex)
            {
                ret = false;
            }
            fileStream.Close();
            tcpClient.Close();
            return ret;
        }

        #endregion

        #region Send Methods

        private void bwSender_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            OnTransferFinished(new FileTransferFinishedEventArgs((bool)e.Result && !e.Cancelled, e.Cancelled, !(bool)e.Result));
            ((BackgroundWorker)sender).Dispose();
            GC.Collect();
        }

        private void bwSender_DoWork(object sender, DoWorkEventArgs e)
        {
            IPEndPoint endpoint = (IPEndPoint)e.Argument;
            TcpClient tcpClient = new TcpClient();
            tcpClient.Connect(endpoint);
            FileTransferProgressChangedEventArgs eventArgs = new FileTransferProgressChangedEventArgs(this.fileAttr);
            OnProgressChanged(eventArgs);
            NetworkStream networkStream = tcpClient.GetStream();
            FileStream fileStream = File.Open(this.fileAttr.FileName, FileMode.Open, FileAccess.Read);
            byte[] buffer = new byte[TransferBytesPerTime];
            int readbyte;
            try
            {
	            while (!this.bwSender.CancellationPending && tcpClient.Client.Connected)
	            {
	                readbyte = fileStream.Read(buffer, 0, buffer.Length);
                    if (readbyte == 0) break;
	                networkStream.Write(buffer, 0, readbyte);
	                OnProgressChanged(eventArgs.Transfer(readbyte));
	            }
                e.Result = true;
            }
            catch (System.Exception ex)
            {
                e.Result = false;
            }
            fileStream.Close();
            tcpClient.Close();
        }

        #endregion

        #region Public Methods

        public int GetPort()
        {
            this.bwListener = new BackgroundWorker();
            this.bwListener.DoWork += new DoWorkEventHandler(bwListener_DoWork);
            this.bwListener.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bwListener_RunWorkerCompleted);
            this.bwListener.WorkerSupportsCancellation = true;
            this.bwListener.RunWorkerAsync();
            this.semaphore.WaitOne();
            return this.listenPort;
        }

        public void DoSend(IPEndPoint remoteEP)
        {
            this.bwSender = new BackgroundWorker();
            this.bwSender.DoWork += new DoWorkEventHandler(bwSender_DoWork);
            this.bwSender.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bwSender_RunWorkerCompleted);
            this.bwSender.WorkerSupportsCancellation = true;
            this.bwSender.RunWorkerAsync(remoteEP);
        }

        public void DoCancel()
        {
            if (this.bwListener != null)
            {
                this.tcpListener.Stop();
                this.bwListener.CancelAsync();
            }
            if (this.bwSender != null)
            {
                this.bwSender.CancelAsync();
            }
        }

        #endregion

        #region Events

        public event FileTransferProgressChangedEventHandler ProgressChanged;
        protected virtual void OnProgressChanged(FileTransferProgressChangedEventArgs e)
        {
            if (ProgressChanged != null)
            {
                Control target = ProgressChanged.Target as Control;
                if (target != null && target.InvokeRequired)
                    target.Invoke(ProgressChanged, new object[] { this, e });
                else
                    ProgressChanged(this, e);
            }
        }

        public event FileTransferFinishedEventHandler TransferFinished;
        protected virtual void OnTransferFinished(FileTransferFinishedEventArgs e)
        {
            if (TransferFinished != null)
            {
                Control target = TransferFinished.Target as Control;
                if (target != null && target.InvokeRequired)
                    target.Invoke(TransferFinished, new object[] { this, e });
                else
                    TransferFinished(this, e);
            }
        }

        #endregion
    }
}
